1 /** 2 Copyright: Copyright (c) 2021, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 10 Copied from dextool. 11 12 Algorithm for detecting what files need to be analyzed based on previous state. 13 */ 14 module code_checker.change; 15 16 import logger = std.experimental.logger; 17 import std.exception : collectException; 18 19 import my.path; 20 import my.optional; 21 import compile_db; 22 23 import code_checker.database : Database, toTrackFile; 24 import code_checker.utility : toAbsoluteRoot; 25 import code_checker.cache; 26 27 /** Returns: the root files that need to be re-analyzed because either them or 28 * their dependency has changed. 29 */ 30 bool[AbsolutePath] dependencyAnalyze(ref Database db, AbsolutePath rootDir, ref FileStatCache fcache) @trusted { 31 import std.algorithm : map, cache, filter; 32 import std.datetime : dur; 33 import std.path : buildPath; 34 import std.typecons : tuple; 35 import std.math : abs; 36 import std.conv : to; 37 import miniorm : spinSql; 38 import code_checker.database : FileId, TrackFile, DepFile; 39 import my.hash : Checksum64; 40 41 typeof(return) rval; 42 43 // pessimistic. Add all as needing to be analyzed. 44 foreach (a; spinSql!(() => db.fileApi.getRootFiles).map!( 45 a => spinSql!(() => db.fileApi.getFile(a)).get)) { 46 auto p = buildPath(rootDir, a.file).AbsolutePath; 47 rval[p] = false; 48 } 49 50 try { 51 auto getTrackFile = (Path p) => spinSql!(() => db.fileApi.getFile(p)); 52 53 TrackFile[Path] dbDeps; 54 foreach (a; spinSql!(() => db.dependencyApi.getAll)) 55 dbDeps[a.file] = a.toTrackFile; 56 57 bool isChanged(T)(T f) nothrow { 58 try { 59 if (!isSame(f.root, f.root.file.AbsolutePath, fcache)) 60 return true; 61 62 foreach (a; f.deps.filter!(a => !isSame(dbDeps[a], 63 toAbsoluteRoot(rootDir, a), fcache))) { 64 logger.tracef("%s dependency changed -> %s", f.root.file, 65 toAbsoluteRoot(rootDir, a)); 66 return true; 67 } 68 69 return false; 70 } catch (Exception e) { 71 logger.trace(e.msg).collectException; 72 } 73 return true; 74 } 75 76 foreach (f; spinSql!(() => db.fileApi).getRootFiles 77 .map!(a => spinSql!(() => db.fileApi.getFile(a)).get) 78 .map!(a => tuple!("root", "deps")(a, spinSql!(() => db.dependencyApi.get(a.file)))) 79 .cache 80 .filter!(a => isChanged(a)) 81 .map!(a => a.root.file)) { 82 rval[buildPath(rootDir, f).AbsolutePath] = true; 83 } 84 } catch (Exception e) { 85 logger.warning(e.msg); 86 } 87 88 debug logger.trace("Dependency analyze: ", rval); 89 90 return rval; 91 } 92 93 /// Convert to an absolute path by finding the first match among the compiler flags 94 Optional!AbsolutePath toAbsolutePath(Path file, AbsolutePath parentDir, 95 AbsolutePath workDir, ParseFlags.Include[] includes, SystemIncludePath[] systemIncludes) @trusted nothrow { 96 import std.algorithm : map, filter; 97 import std.file : exists, isDir; 98 import std.path : buildPath; 99 100 Optional!AbsolutePath lookup(string dir) nothrow { 101 const p = buildPath(dir, file); 102 try { 103 if (exists(p) && !isDir(p)) 104 return some(AbsolutePath(p)); 105 } catch (Exception e) { 106 } 107 return none!AbsolutePath; 108 } 109 110 { 111 auto a = lookup(parentDir.toString); 112 if (a.hasValue) 113 return a; 114 } 115 116 { 117 auto a = lookup(workDir.toString); 118 if (a.hasValue) 119 return a; 120 } 121 122 foreach (a; includes.map!(a => lookup(a.payload)) 123 .filter!(a => a.hasValue)) { 124 return a; 125 } 126 127 foreach (a; systemIncludes.map!(a => lookup(a.value)) 128 .filter!(a => a.hasValue)) { 129 return a; 130 } 131 132 return none!AbsolutePath; 133 }